//
//  HWDotProduct.cpp
//  sad
//
//  Created by Christopher Kemsley on 2/26/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//

#include <string>

#include "SystemCDefinitions.hpp"

#include "Settings.hpp"
#include "Exception.hpp"
#include "BusInterface.hpp"

#include "ModuleFactory.hpp"
#include "PubSub.hpp"
#include "Codec.hpp"

#include "Log.hpp"




/*
 Memory mapping:
 
 (base+0)		read/write		input1stride
 (base+1)		read/write		input2stride
 (base+2)		read/write		length
 (base+3)		read/write		input1address
 (base+4)		read/write		input2address
 (base+5)		readonly		done
 (base+6)		readonly		value
 */


enum {
	kDeltaInput1Stride		=	0 ,
	kDeltaInput2Stride		=	1 ,
	
	kDeltaLength			=	2 ,
	
	kDeltaInput1Address		=	3 ,
	kDeltaInput2Address		=	4 ,
	
	kDeltaDone				=	5 ,
	kDeltaValue				=	6 ,
	
	kDeltaMaxForRead		=	7 ,
	kDeltaMaxForWrite		=	5
} ;








struct	HWDotProductSettings : public Settings
{
	
	/* Memory-mapped settings */
	/* These are not validated since they can be runtime-configurable */
	unsigned	input1stride ;
	unsigned	input2stride ;
	unsigned	length ;
	
	/* Linking settings */
	std::string	busMasterName ;
	std::string	busSlaveName ;
	
	unsigned	mappingBaseAddress ;
	
	
	HWDotProductSettings ( std::string const & name )
	:	Settings(name)
	,	input1stride(1)
	,	input2stride(1)
	,	length(0)
	,	busMasterName()
	,	busSlaveName()
	,	mappingBaseAddress(0)
		{
		add ( "mappingBaseAddress" , mappingBaseAddress ) ;
		
		add ( "masterName" , busMasterName ) ;
		add ( "slaveName" , busSlaveName ) ;
		
		add ( "input1stride" , input1stride ) ;
		add ( "input2stride" , input2stride ) ;
		add ( "length" , length ) ;
		}
	
	
	virtual void		validate ()
		{
		if ( busMasterName.size() == 0 )
			ThrowException ( "HWDotProductSettings" , "busMasterName has zero-length" , __FILE__,__LINE__) ;
		if ( busSlaveName.size() == 0 )
			ThrowException ( "HWDotProductSettings" , "busSlaveName has zero-length" , __FILE__,__LINE__ ) ;
		}
} ;








struct	HWDotProduct : public ConfiguredModel<HWDotProductSettings>
{
	/* Memory-mapped settings */
	unsigned	input1address ;
	unsigned	input2address ;
	
	unsigned	done ;
	unsigned	result ;
	
	
	/* Internal cross-function flags */
	bool		autostart ;
	sc_event	startEvent ;
	
	
	/* Linking */
	sc_port<Bus::MasterInterface>	busMaster ;
	sc_port<Bus::SlaveInterface>	busSlave ;
	
	
	
	
	
	SC_HAS_PROCESS(HWDotProduct) ;
	
	HWDotProduct ( ModelVariant const & name )
	:	ConfiguredModel<HWDotProductSettings>(name)
	,	input1address(0)
	,	input2address(0)
	,	done(0)
	,	autostart(false)
	,	startEvent()
	,	busMaster()
	,	busSlave()
		{
		Subscription<Bus::MasterInterface>	pSubMaster	( mConfig.busMasterName ) ;
		Subscription<Bus::SlaveInterface>	pSubSlave	( mConfig.busSlaveName ) ;
		
		bindPortToInterface ( &busMaster , pSubMaster ) ;
		bindPortToInterface ( &busSlave , pSubSlave ) ;
		
		
		SC_THREAD ( runloop ) ;
		SC_THREAD ( controlLoop ) ;
		}
	
	
	
	
	
	
	void	controlLoop ()
		{
		Bus::Request	request ;
		
		while ( 1 )
			{
			Log::states("HWDotProduct") << MethodId(HWDotProduct,runloop) << "Waiting for request..." ;
			
			
			// Wait for a request
			while ( ! _doesRecognizeRequest ( request ) )
				busSlave -> waitForNewRequest ( request ) ;
			
			Log::states("HWDotProduct") << MethodId(HWDotProduct,runloop) << "Got a request we recognize! Woot! Acknowledging." ;
			
			busSlave -> acknowledgeRequest() ;
			
			Log::states("HWDotProduct") << MethodId(HWDotProduct,runloop) << "Handling the request." ;
			
			_handleRequest ( request ) ;
			request.invalidate() ;
			
			if ( autostart )
				{
				done = 0 ;
				startEvent . notify ( 0 , kSCIntervalSeconds ) ;
				autostart = false ;
				}
			}
		}
	
	void	runloop ()
		{
		while ( 1 )
			{
			wait ( startEvent ) ;
			
			_runAlgorithm () ;
			}
		}
	
	
	bool	_doesRecognizeRequest	( Bus::Request const & request )
		{
		Log::states("HWDotProduct") << "Checking request to see if it's ours" ;
		
		if ( request.isValid() )
			{
			Log::states("HWDotProduct") << "The request is valid... that's a start: " << request ;
			Log::states("HWDotProduct") << "Our base is " << mConfig.mappingBaseAddress ;
			
			Bus::Address	max		=	(request.operation==Bus::kOperationRead) ? kDeltaMaxForRead : kDeltaMaxForWrite ;
			Bus::Address	delta	=	request.baseAddress - mConfig.mappingBaseAddress ;
			
			Log::states("HWDotProduct") << "Max delta for this operation type is " << max ;
			
			if ( delta < max )
				{
				delta += request.blockLength ;
				if ( delta <= max )
					{
					return true ;
					}
				}
			}
		return false ;
		}
	
	
	void	_handleRequest			( Bus::Request & request )
		{
		Log::states("HWDotProduct") << "Handling " << request ;
		
		if ( request.operation == Bus::kOperationRead )
			_handleReadRequest(request) ;
		else
			_handleWriteRequest(request) ;
		}
	
	void	_handleReadRequest		( Bus::Request & request )
		{
		Log::states("HWDotProduct") << "Handling as READ request: " << request ;
		
		Bus::Address	here	=	request.baseAddress - mConfig.mappingBaseAddress ;
		Bus::Address	end		=	here + request.blockLength ;
		
		while ( here < end )
			_handleRead ( here++ ) ;
		}
	void	_handleWriteRequest		( Bus::Request & request )
		{
		Log::states("HWDotProduct") << "Handling as WRITE request: " << request ;
		
		Bus::Address	here	=	request.baseAddress - mConfig.mappingBaseAddress ;
		Bus::Address	end		=	here + request.blockLength ;
		
		while ( here < end )
			_handleWrite ( here++ ) ;
		}
	
	
	void	_handleRead		( Bus::Address const & address )
		{
		Bus::Word value ;
		
		switch ( address )
			{
				case kDeltaInput1Stride : {
					Log::states("HWDotProduct") << "Reading kDeltaInput1Stride" ;
					value = mConfig.input1stride ;
				} break ;
				
				case kDeltaInput2Stride : {
					Log::states("HWDotProduct") << "Reading kDeltaInput2Stride" ;
					value = mConfig.input2stride ;
				} break ;
				
				case kDeltaInput1Address : {
					Log::states("HWDotProduct") << "Reading kDeltaInput1Address" ;
					value = input1address ;
				} break ;
				
				case kDeltaInput2Address : {
					Log::states("HWDotProduct") << "Reading kDeltaInput2Address" ;
					value = input2address ;
				} break ;
				
				case kDeltaLength : {
					Log::states("HWDotProduct") << "Reading kDeltaLength" ;
					value = mConfig.length ;
				} break ;
				
				case kDeltaDone : {
					Log::states("HWDotProduct") << "Reading kDeltaDone" ;
					value = done ;
				} break ;
				
				case kDeltaValue : {
					Log::states("HWDotProduct") << "Reading kDeltaValue" ;
					value = result ;
				} break ;
			}
		
		busSlave -> writeWord ( value ) ;
		}
	void	_handleWrite	( Bus::Address const & address )
		{
		Bus::Word value ;
		busSlave -> readWord ( value ) ;
		
		switch ( address )
			{
				case kDeltaInput1Stride : {
					mConfig.input1stride = value ;
				} break ;
				
				case kDeltaInput2Stride : {
					mConfig.input2stride = value ;
				} break ;
				
				case kDeltaInput1Address : {
					input1address = value ;
				} break ;
				
				case kDeltaInput2Address : {
					input2address = value ;
					autostart = true ;
				} break ;
				
				case kDeltaLength : {
					mConfig.length = value ;
				} break ;
			}
		}
	
	
	
	void	_runAlgorithm	()
		{
		Log::states("HWDotProduct") << "Running algorithm" ;
		
		unsigned	sum		=	0 ; // running-sum
		
		unsigned	length	=	mConfig.length ;
		
		unsigned	block1 [length] ;
		unsigned	block2 [length] ;
		
		unsigned*	here1	=	block1 ;
		unsigned*	here2	=	block2 ;
		
		
		Log::states("HWDotProduct") << "Reading block 1" ;
		_readBlock ( input1address , mConfig.input1stride , mConfig.length , block1 ) ;
		Log::states("HWDotProduct") << "DONE Reading block 1" ;
		
		Log::states("HWDotProduct") << "Reading block 2" ;
		_readBlock ( input2address , mConfig.input2stride , mConfig.length , block2 ) ;
		Log::states("HWDotProduct") << "DONE Reading block 2" ;
		
		while ( length-- != 0 )
			{
			Log::states("HWDotProduct") << "sum += " << (*here1) << " * " << (*here2) ;
			sum  +=  *(here1++)  *  *(here2++)  ;
			}
		
		Log::states("HWDotProduct") << "Done! Recording that fact. Sum: " << sum ;
		result = sum ;
		done = 1 ;
		}
	
	void	_readBlock	( unsigned address , unsigned stride , unsigned length , unsigned * buffer )
		{
		/* If the stride is 1, we'll use a burst-request */
		if ( stride == 1 )
			{
			Bus::Request	request	( address , Bus::kOperationRead , length ) ;
			
			busMaster -> request ( Bus::uniqueAddress ( static_cast<void*>(this) ) , request ) ;
			if ( busMaster -> waitForAcknowledge ( Bus::uniqueAddress ( static_cast<void*>(this) ) ) )
				{
				while ( length-- != 0 )
					{
					Bus::Word value ;
					busMaster -> readWord ( value ) ;
					*(buffer++) = value ;
					}
				}
			else
				Log::error("HWDotProduct") << "Read operation failed! " << request ;
			}
		
		
		/* Optional (not implemented): If the stride is small, we can still use a burst request to eliminate sync */
		
		
		/* If the stride isn't 1, we'll use a one-by-one request */
		else
			{
			Bus::Word value ;
			
			while ( length-- != 0 )
				{
				Bus::Request	request	( address , Bus::kOperationRead , 1 ) ;
				
				busMaster -> request ( Bus::uniqueAddress ( static_cast<void*>(this) ) , request ) ;
				if ( busMaster -> waitForAcknowledge ( Bus::uniqueAddress ( static_cast<void*>(this) ) ) )
					{
					busMaster -> readWord ( value ) ;
					*(buffer++) = value ;
					}
				else
					Log::error("HWDotProduct") << "Read operation failed! " << request ;
				
				address += stride ;
				}
			}
		}
} ;




TypedModuleMaker<HWDotProduct>	_HWDotProduct ( "accelerate.dotProduct.bus" ) ;
